#include <string.h>
#include <jni.h>
#include <android/log.h>
#include <gst/gst.h>
#include <pthread.h>
#include <gio/gio.h>


GST_DEBUG_CATEGORY_STATIC (debug_category);
#define GST_CAT_DEFAULT debug_category

/*
 * These macros provide a way to store the native pointer to CustomData, which might be 32 or 64 bits, into
 * a jlong, which is always 64 bits, without warnings.
 */
#if GLIB_SIZEOF_VOID_P == 8
# define GET_CUSTOM_DATA(env, thiz, fieldID) (CustomData *)(*env)->GetLongField (env, thiz, fieldID)
# define SET_CUSTOM_DATA(env, thiz, fieldID, data) (*env)->SetLongField (env, thiz, fieldID, (jlong)data)
#else
# define GET_CUSTOM_DATA(env, thiz, fieldID) (CustomData *)(jint)(*env)->GetLongField (env, thiz, fieldID)
# define SET_CUSTOM_DATA(env, thiz, fieldID, data) (*env)->SetLongField (env, thiz, fieldID, (jlong)(jint)data)
#endif

/* Structure to contain all our information, so we can pass it to callbacks */
typedef struct _CustomData {
	jobject app;           /* Application instance, used to call its methods. A global reference is kept. */
	GstElement *pipeline;  /* The running pipeline */
	GMainContext *context; /* GLib context used to run the main loop */
	GMainLoop *main_loop;  /* GLib main loop */
	gboolean initialized;  /* To avoid informing the UI multiple times about the initialization */
} CustomData;

/* These global variables cache values which are not changing during execution */
static pthread_t gst_app_thread;
static pthread_key_t current_jni_env;
static JavaVM *java_vm;
static jfieldID custom_data_field_id;
static jmethodID set_message_method_id;
static jmethodID on_gstreamer_initialized_method_id;
static jmethodID set_port_method_id;

char * txtPipeline;
char tmp[500];
int rcv;
int snd;

/*
 * Private methods
 */

/* Register this thread with the VM */
static JNIEnv *attach_current_thread (void) {
	JNIEnv *env;
	JavaVMAttachArgs args;

	GST_DEBUG ("Attaching thread %p", g_thread_self ());
	args.version = JNI_VERSION_1_4;
	args.name = NULL;
	args.group = NULL;

	if ((*java_vm)->AttachCurrentThread (java_vm, &env, &args) < 0) {
		GST_ERROR ("Failed to attach current thread");
		return NULL;
	}

	return env;
}

/* Unregister this thread from the VM */
static void detach_current_thread (void *env) {
	GST_DEBUG ("Detaching thread %p", g_thread_self ());
	(*java_vm)->DetachCurrentThread (java_vm);
}

/* Retrieve the JNI environment for this thread */
static JNIEnv *get_jni_env (void) {
	JNIEnv *env;

	if ((env = pthread_getspecific (current_jni_env)) == NULL) {
		env = attach_current_thread ();
		pthread_setspecific (current_jni_env, env);
	}

	return env;
}

/* Change the content of the UI's TextView */
static void set_ui_message (const gchar *message, CustomData *data) {
	JNIEnv *env = get_jni_env ();
	GST_DEBUG ("Setting message to: %s", message);
	jstring jmessage = (*env)->NewStringUTF(env, message);
	(*env)->CallVoidMethod (env, data->app, set_message_method_id, jmessage);
	if ((*env)->ExceptionCheck (env)) {
		GST_ERROR ("Failed to call Java method");
		(*env)->ExceptionClear (env);
	}
	(*env)->DeleteLocalRef (env, jmessage);
}

/*Set port receive stream*/
static void set_port_receive_stream(const gchar *port, CustomData *data){
	JNIEnv *env = get_jni_env ();
	GST_DEBUG ("Port à remonter: %s", port);
	jstring jmessage = (*env)->NewStringUTF(env, port);
	(*env)->CallVoidMethod (env, data->app, set_port_method_id, jmessage);
	if ((*env)->ExceptionCheck (env)) {
		GST_ERROR ("Failed to call Java method");
		(*env)->ExceptionClear (env);
	}
	(*env)->DeleteLocalRef (env, jmessage);
}

/* Retrieve errors from the bus and show them on the UI */
static void error_cb (GstBus *bus, GstMessage *msg, CustomData *data) {
	GError *err;
	gchar *debug_info;
	gchar *message_string;

	gst_message_parse_error (msg, &err, &debug_info);
	message_string = g_strdup_printf ("Error received from element %s: %s", GST_OBJECT_NAME (msg->src), err->message);
	g_clear_error (&err);
	g_free (debug_info);
	set_ui_message (message_string, data);
	g_free (message_string);
	gst_element_set_state (data->pipeline, GST_STATE_NULL);
}

/* Notify UI about pipeline state changes */
static void state_changed_cb (GstBus *bus, GstMessage *msg, CustomData *data) {
	GstState old_state, new_state, pending_state;
	gst_message_parse_state_changed (msg, &old_state, &new_state, &pending_state);
	/* Only pay attention to messages coming from the pipeline, not its children */
	if (GST_MESSAGE_SRC (msg) == GST_OBJECT (data->pipeline)) {
		gchar *message = g_strdup_printf("State changed to %s", gst_element_state_get_name(new_state));
		set_ui_message(message, data);
		g_free (message);
	}
}

/* Check if all conditions are met to report GStreamer as initialized.
 * These conditions will change depending on the application */
static void check_initialization_complete (CustomData *data) {
	JNIEnv *env = get_jni_env ();
	if (!data->initialized && data->main_loop) {
		GST_DEBUG ("Initialization complete, notifying application. main_loop:%p", data->main_loop);
		(*env)->CallVoidMethod (env, data->app, on_gstreamer_initialized_method_id);
		if ((*env)->ExceptionCheck (env)) {
			GST_ERROR ("Failed to call Java method");
			(*env)->ExceptionClear (env);
		}
		data->initialized = TRUE;
	}
}

/* Main method for the native code. This is executed on its own thread. */
static void *app_function (void *userdata) {
	JavaVMAttachArgs args;
	GstBus *bus;
	CustomData *data = (CustomData *)userdata;
	GSource *bus_source;
	GError *error = NULL;

	GST_DEBUG ("Creating pipeline in CustomData at %p", data);

	/* Create our own GLib Main Context and make it the default one */
	data->context = g_main_context_new ();
	g_main_context_push_thread_default(data->context);

	/* Build pipeline */
	//data->pipeline = gst_parse_launch("udpsrc port=10000 caps=\"application/x-rtp, media=(string)audio, clock-rate=(int)44100, encoding-name=(string)SPEEX, "
	//  "encoding-params=(string)1, payload=(int)110\" ! gstrtpjitterbuffer latency=50 do-lost=true ! rtpspeexdepay ! speexdec ! audioconvert ! audioresample ! openslessink", &error);
	data->pipeline = gst_parse_launch(txtPipeline, &error);
	if (error) {
		gchar *message = g_strdup_printf("Unable to build pipeline: %s", error->message);
		g_clear_error (&error);
		set_ui_message(message, data);
		g_free (message);
		return NULL;
	}

	/*Appel du java pour stocker le port du stream entrant*/
	if(rcv==1){
			gint pp=0;
			GstElement *udpsrc ;
			udpsrc = gst_bin_get_by_name (GST_BIN (data->pipeline), "myudp");
			g_object_get(udpsrc, "port", &pp);
			char str[50];
			snprintf(str, 50, "%d", (int)pp);
			char * mystring;
			mystring = str;
			GST_DEBUG("################## port is %d\n",pp);
			/*set_port_receive_stream(g_strdup_printf (mystring), data);*/
			set_port_receive_stream(mystring, data);
		}
	/*if(snd==1){
			gint pp=0;
			GstElement *udpsrc ;
			udpsrc = gst_bin_get_by_name (GST_BIN (data->pipeline), "myudp");
			g_object_get(udpsrc, "bind-port", &pp);
			char str[50];
			snprintf(str, 50, "%d", (int)pp);
			char * mystring;
			mystring = str;
			GST_DEBUG("################## bind-port is %d\n",pp);
			set_port_receive_stream(mystring, data);
		}*/
	if(snd==1){
				gint pp=0;
				/*GSocket * soc;
				sock = g_socket_new(G_SOCKET_FAMILY_IPV4,
				                    G_SOCKET_TYPE_DATAGRAM,
				                    G_SOCKET_PROTOCOL_UDP,
				                    &err);
				g_assert(err == NULL);

				GstElement *udpsrc ;
				udpsrc = gst_bin_get_by_name (GST_BIN (data->pipeline), "myudp");
				g_object_get(udpsrc, "used-socket", soc);
				*/
				char str[50];
				snprintf(str, 50, "%d", (int)pp);
				char * mystring;
				mystring = str;
				GST_DEBUG("################## bind-port is %d\n",pp);
				set_port_receive_stream(mystring, data);
			}

	/* Instruct the bus to emit signals for each received message, and connect to the interesting signals */
	bus = gst_element_get_bus (data->pipeline);
	bus_source = gst_bus_create_watch (bus);
	g_source_set_callback (bus_source, (GSourceFunc) gst_bus_async_signal_func, NULL, NULL);
	g_source_attach (bus_source, data->context);
	g_source_unref (bus_source);
	g_signal_connect (G_OBJECT (bus), "message::error", (GCallback)error_cb, data);
	g_signal_connect (G_OBJECT (bus), "message::state-changed", (GCallback)state_changed_cb, data);
	gst_object_unref (bus);

	/* Create a GLib Main Loop and set it to run */
	GST_DEBUG ("Entering main loop... (CustomData:%p)", data);
	data->main_loop = g_main_loop_new (data->context, FALSE);
	check_initialization_complete (data);
	g_main_loop_run (data->main_loop);
	GST_DEBUG ("Exited main loop");
	g_main_loop_unref (data->main_loop);
	data->main_loop = NULL;

	/* Free resources */
	g_main_context_pop_thread_default(data->context);
	g_main_context_unref (data->context);
	gst_element_set_state (data->pipeline, GST_STATE_NULL);
	gst_object_unref (data->pipeline);

	return NULL;
}

/*
 * Java Bindings
 */

/* Instruct the native code to create its internal data structure, pipeline and thread */
static void gst_native_init (JNIEnv* env, jobject thiz) {
	CustomData *data = g_new0 (CustomData, 1);
	SET_CUSTOM_DATA (env, thiz, custom_data_field_id, data);
	GST_DEBUG_CATEGORY_INIT (debug_category, "tutorial-2", 0, "Android tutorial 2");
	gst_debug_set_threshold_for_name("tutorial-2", GST_LEVEL_DEBUG);
	GST_DEBUG ("Created CustomData at %p", data);
	data->app = (*env)->NewGlobalRef (env, thiz);
	GST_DEBUG ("Created GlobalRef for app object at %p", data->app);
	pthread_create (&gst_app_thread, NULL, &app_function, data);
}

static void gst_native_init_send_stream (JNIEnv* env, jobject thiz, jstring ip, jstring port){

	const char * ip2 = (*env)->GetStringUTFChars(env, ip, (jboolean *)0);
	const char * port2 = (*env)->GetStringUTFChars(env, port, (jboolean *)0);
	GST_DEBUG ("mon port : %s" , port2);
	GST_DEBUG ("mon ip : %s" , ip2);
	/*(*env)->ReleaseStringUTFChars(res, str);*/
	strcpy(tmp,"");
	/*strcat(strcat(strcat(strcat(tmp, "openslessrc ! audioresample ! audio/x-raw, rate=6000, channels=1 ! audioconvert ! speexenc quality=10 ! rtpspeexpay ! udpsink name=myudp host="), ip2)," port="),port2);*/
	strcat(strcat(strcat(strcat(tmp, "openslessrc ! audioresample ! audio/x-raw, rate=6000, channels=1 ! audioconvert ! speexenc quality=10 ! rtpspeexpay ! multiudpsink name=myudp clients="), ip2),":"),port2);
	GST_DEBUG ("mon send pipeline : %s" , tmp);
	/*txtPipeline = "openslessrc ! audioresample ! audio/x-raw, rate=6000, channels=1 ! audioconvert ! speexenc quality=4 ! rtpspeexpay ! udpsink host=192.168.1.56 port=10001";
	 */
	txtPipeline = tmp;
	(*env)->ReleaseStringUTFChars(env, ip, ip2);
	(*env)->ReleaseStringUTFChars(env, port, port2);
	rcv=0;
	snd=1;
	gst_native_init(env, thiz);
}

static void gst_native_init_receive_stream (JNIEnv* env, jobject thiz){
	//txtPipeline = "udpsrc port=10001 caps=\"application/x-rtp, media=(string)audio, clock-rate=(int)6000, encoding-name=(string)SPEEX, encoding-params=(string)1, payload=(int)110\" ! rtpjitterbuffer latency=50 do-lost=true ! rtpspeexdepay ! speexdec ! audioconvert ! audioresample ! openslessink";

	txtPipeline="udpsrc name=myudp caps=\"application/x-rtp, media=(string)audio, clock-rate=(int)6000, encoding-name=(string)SPEEX, encoding-params=(string)1, payload=(int)110\" ! rtpjitterbuffer latency=50 do-lost=true ! rtpspeexdepay ! speexdec ! audioconvert ! audioresample ! openslessink";
	GST_DEBUG ("mon receive pipeline : %s" , txtPipeline);
	rcv=1;
	snd=0;
	gst_native_init(env, thiz);
}



/* Quit the main loop, remove the native thread and free resources */
static void gst_native_finalize (JNIEnv* env, jobject thiz) {
	CustomData *data = GET_CUSTOM_DATA (env, thiz, custom_data_field_id);
	if (!data) return;
	GST_DEBUG ("Quitting main loop...");
	g_main_loop_quit (data->main_loop);
	GST_DEBUG ("Waiting for thread to finish...");
	pthread_join (gst_app_thread, NULL);
	GST_DEBUG ("Deleting GlobalRef for app object at %p", data->app);
	(*env)->DeleteGlobalRef (env, data->app);
	GST_DEBUG ("Freeing CustomData at %p", data);
	g_free (data);
	SET_CUSTOM_DATA (env, thiz, custom_data_field_id, NULL);
	GST_DEBUG ("Done finalizing");
}

/* Set pipeline to PLAYING state */
static void gst_native_play (JNIEnv* env, jobject thiz) {
	CustomData *data = GET_CUSTOM_DATA (env, thiz, custom_data_field_id);
	if (!data) return;
	GST_DEBUG ("Setting state to PLAYING");
	gst_element_set_state (data->pipeline, GST_STATE_PLAYING);
}

/* Set pipeline to PAUSED state */
static void gst_native_pause (JNIEnv* env, jobject thiz) {
	CustomData *data = GET_CUSTOM_DATA (env, thiz, custom_data_field_id);
	if (!data) return;
	GST_DEBUG ("Setting state to PAUSED");
	gst_element_set_state (data->pipeline, GST_STATE_PAUSED);
}

/* Static class initializer: retrieve method and field IDs */
static jboolean gst_native_class_init (JNIEnv* env, jclass klass) {
	custom_data_field_id = (*env)->GetFieldID (env, klass, "native_custom_data", "J");
	set_message_method_id = (*env)->GetMethodID (env, klass, "setMessage", "(Ljava/lang/String;)V");
	on_gstreamer_initialized_method_id = (*env)->GetMethodID (env, klass, "onGStreamerInitialized", "()V");
	set_port_method_id = (*env)->GetMethodID (env, klass, "setPort", "(Ljava/lang/String;)V");

	if (!custom_data_field_id || !set_message_method_id || !on_gstreamer_initialized_method_id) {
		/* We emit this message through the Android log instead of the GStreamer log because the later
		 * has not been initialized yet.
		 */
		__android_log_print (ANDROID_LOG_ERROR, "tutorial-2", "The calling class does not implement all necessary interface methods");
		return JNI_FALSE;
	}
	return JNI_TRUE;
}


/* List of implemented native methods */
static JNINativeMethod native_methods[] = {
		{ "nativeInitSendStream", "(Ljava/lang/String;Ljava/lang/String;)V", (void *) gst_native_init_send_stream},
		{ "nativeInitReceiveStream", "()V", (void *) gst_native_init_receive_stream},
		{ "nativeFinalize", "()V", (void *) gst_native_finalize},
		{ "nativePlay", "()V", (void *) gst_native_play},
		{ "nativePause", "()V", (void *) gst_native_pause},
		{ "nativeClassInit", "()Z", (void *) gst_native_class_init}
};

/* Library initializer */
jint JNI_OnLoad(JavaVM *vm, void *reserved) {
	JNIEnv *env = NULL;

	java_vm = vm;

	if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_4) != JNI_OK) {
		__android_log_print (ANDROID_LOG_ERROR, "stream", "Could not retrieve JNIEnv");
		return 0;
	}
	jclass klass = (*env)->FindClass (env, "com/interphone/beta/stream/Stream");
	(*env)->RegisterNatives (env, klass, native_methods, G_N_ELEMENTS(native_methods));

	pthread_key_create (&current_jni_env, detach_current_thread);

	return JNI_VERSION_1_4;
}

/*----------------------------------------------*/

